import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;


public class HttpProxyServer {
	//what information we will need to connect to a host
	private final String cliAddr, servAddr;
	private final int cliPort, servPort;
	
	//the sockets and streams we will be using once we have connected
	private final Socket server, client; //TODO consider the complicated switch to DatagramSocket if TCP is too slow.
	private final InputStream fromServer, fromClient;
	private final OutputStream toServer, toClient;
	
	//thread-related members
	private final Thread[] serverThreads = new Thread[2];
	private boolean stopRequested = false;

	//constructor
	public HttpProxyServer(String clientAddr, int clientPort, String serverAddr, int serverPort) throws UnknownHostException, IOException {
		//store the input data
		this.cliAddr = clientAddr;
		this.cliPort = clientPort;
		this.servAddr = serverAddr;
		this.servPort = serverPort;
		
		//use the input data to initialize the other immutable fields
		this.client = new Socket(this.cliAddr, this.cliPort);
		this.server = new Socket(this.servAddr, this.servPort);

		this.fromServer = this.server.getInputStream();
		this.toServer = this.server.getOutputStream();
		this.fromClient = this.client.getInputStream();
		this.toClient = this.client.getOutputStream();
		
		//initialize the runners
		Thread clientToServer = new Thread() {
			private byte[] buf = new byte[4096];
			private int numBytesRead;
			
			public void run() {
				//read from the client and write to the server
				try {
					while( ( !setAndReturnStopStatus(false) ) && (this.numBytesRead = fromClient.read(this.buf)) != -1 ) {
						toServer.write(this.buf, 0, this.numBytesRead);
						toServer.flush();
					}
				}catch(IOException ioErr) { ioErr.printStackTrace(); }
				
				//break down the streams
				requestStop();
				try { fromServer.close(); toClient.close(); serverThreads[1].join(); }
				catch(IOException ioErr) { ioErr.printStackTrace(); }
				catch(InterruptedException intErr) { intErr.printStackTrace(); }
			}
		};
		
		Thread serverToClient = new Thread() {
			private byte[] buf = new byte[51200];
			private int numBytesRead;
			
			public void run() {
				//read from the server and write to the client
				try {
					while( ( !setAndReturnStopStatus(false) ) && (this.numBytesRead = fromServer.read(this.buf)) != -1 ) {
						toClient.write(this.buf, 0, this.numBytesRead);
						toClient.flush();
					}
				}catch(IOException ioErr) { ioErr.printStackTrace(); }
				
				//break down the streams
				requestStop();
				try { fromServer.close(); toClient.close(); serverThreads[0].join(); }
				catch(IOException ioErr) { ioErr.printStackTrace(); }
				catch(InterruptedException intErr) { intErr.printStackTrace(); }
			}
		};
		
		this.serverThreads[0] = clientToServer;
		this.serverThreads[1] = serverToClient;
		
		//begin running
		clientToServer.start();
		serverToClient.start();
	}
	
	public void stopServer() {
		//signal the threads to stop
		this.setAndReturnStopStatus(true);
	}
	
	private boolean setAndReturnStopStatus(boolean stopNowRequested) {
		if(stopNowRequested) {
			this.requestStop();
			return true;
		}
		else {
			return false;
		}
	}
	
	private synchronized void requestStop() {
		this.stopRequested = true;
	}
}
